#include "Tank.h"
#include "GameScene.h"
#include "Game.h"
#include "Bullet.h"

#define MIN_CORRECT_SIZE 30
#define MAP_SIZE 780

Tank::Tank(float x, float y, float w, float h, Angle angle, float speed, GameScene *scene)
    : MoveObject(x, y, w, h, angle, speed), scene_(scene)
{
    prevAngle_ = angle_;
}

void Tank::Move(float dt)
{
    isRunning_ = true;
    switch (angle_)
    {
    case Angle::UP:
        moveUp(dt);
        break;
    case Angle::RIGHT:
        moveRight(dt);
        break;
    case Angle::DOWN:
        moveDown(dt);
        break;
    case Angle::LEFT:
        moveLeft(dt);
        break;
    default:
        break;
    }
    prevAngle_ = angle_;
}

void Tank::moveLeft(float dt)
{
    if(prevAngle_ == Angle::UP || prevAngle_ == Angle::DOWN)
    {
        correctY();
    }
    x_ -= dt*speed_;
    SDL_Point crashPos[2] = {{static_cast<int>(x_), static_cast<int>(y_ + 1)},
                             {static_cast<int>(x_), static_cast<int>(y_ + height_ - 1)}};
    checkCrashWithMargin();
    if(checkCrashWithTerrain(crashPos))
    {
        x_ = crashPos[0].x + MIN_CORRECT_SIZE;
    }
    else if(checkCrashWithOtherTanks(crashPos[0]))
    {
        x_ = crashPos[0].x + width_;
    }
}

void Tank::moveRight(float dt)
{
    if(prevAngle_ == Angle::UP || prevAngle_ == Angle::DOWN)
    {
        correctY();
        return;
    }
    x_ += dt*speed_;
    SDL_Point crashPos[2] = {{static_cast<int>(x_ + width_), static_cast<int>(y_ + 1)},
                             {static_cast<int>(x_ + width_), static_cast<int>(y_ + height_ - 1)}};
    checkCrashWithMargin();
    if(checkCrashWithTerrain(crashPos))
    {
        x_ = crashPos[0].x - width_;
    }
    else if(checkCrashWithOtherTanks(crashPos[0]))
    {
        x_ = crashPos[0].x - width_;
    }
}

void Tank::moveUp(float dt)
{
    if(prevAngle_ == Angle::LEFT || prevAngle_ == Angle::RIGHT)
    {
        correctX();
        return;
    }
    y_ -= dt*speed_;
    SDL_Point crashPos[2] = {{static_cast<int>(x_ + 1), static_cast<int>(y_)},
                             {static_cast<int>(x_ + width_ - 1), static_cast<int>(y_)}};
    checkCrashWithMargin();
    if(checkCrashWithTerrain(crashPos))
    {
        y_ = crashPos[0].y + MIN_CORRECT_SIZE;
    }
    else if(checkCrashWithOtherTanks(crashPos[0]))
    {
        y_ = crashPos[0].y + height_;
    }
}

void Tank::moveDown(float dt)
{
    if(prevAngle_ == Angle::LEFT || prevAngle_ == Angle::RIGHT)
    {
        correctX();
        return;
    }
    y_ += dt*speed_;
    SDL_Point crashPos[2] = {{static_cast<int>(x_ + 1), static_cast<int>(y_ + height_)},
                             {static_cast<int>(x_ + width_ - 1), static_cast<int>(y_ + height_)}};
    checkCrashWithMargin();
    checkCrashWithTerrain(crashPos);
    if(checkCrashWithTerrain(crashPos))
    {
        y_ = crashPos[0].y - height_;
    }
    else if(checkCrashWithOtherTanks(crashPos[0]))
    {
        y_ = crashPos[0].y - height_;
    }
}

void Tank::correctX()
{
    int x = static_cast<int>(x_);
    int X = x / MIN_CORRECT_SIZE;
    int diff = x % MIN_CORRECT_SIZE;
    diff = diff > 15 ? 30 : 0;
    x_ = X * MIN_CORRECT_SIZE + diff;
}

void Tank::correctY()
{
    int y = static_cast<int>(y_);
    int Y = y / MIN_CORRECT_SIZE;
    int diff = y % MIN_CORRECT_SIZE;
    diff = diff > 15 ? 30 : 0;
    y_ = Y * MIN_CORRECT_SIZE + diff;
}

bool Tank::checkCrashWithTerrain(SDL_Point (&crashPos)[2])
{
    Terrain* terrain[2];
    terrain[0] = scene_->GetTerrain(crashPos[0].x, crashPos[0].y);
    terrain[1] = scene_->GetTerrain(crashPos[1].x, crashPos[1].y);

    bool crash = false;
    if(terrain[0] && terrain[1])
    {
        for (int i = 0; i < 2; i++)
        {
            switch (terrain[i]->type)
            {
            case Water:
                crash = true;
                break;
            case Brick:
            case Iron:
            {
                int b_x = (crashPos[i].x / MIN_CORRECT_SIZE) % 2;
                int b_y = (crashPos[i].y / MIN_CORRECT_SIZE) % 2;
                uint16_t bit = b_x ? 0x3333 : 0xCCCC;
                bit = bit & (b_y ? 0x00FF : 0xFF00);
                if (bit & terrain[i]->block_bit)
                {
                    crash = true;
                }
            }
            break;
            default:
                break;
            }
        }
    }
    if(crash)
    {
        crashPos[0].x = crashPos[0].x / MIN_CORRECT_SIZE;
        crashPos[0].y = crashPos[0].y / MIN_CORRECT_SIZE;
        crashPos[0].x *= MIN_CORRECT_SIZE;
        crashPos[0].y *= MIN_CORRECT_SIZE;
    }
    return crash;
}

bool Tank::checkCrashWithOtherTanks(SDL_Point& otherPos)
{
    auto& tanks = scene_->GetTanks();
    for(auto& tank:tanks)
    {
        if(this == tank.get()) continue;
        if(SDL_HasIntersection(&GetRect(), &tank->GetRect()))
        {
            otherPos.x = static_cast<int>(tank->x_);
            otherPos.y = static_cast<int>(tank->y_);
            return true;
        }
    }
    return false;
}

void Tank::checkCrashWithMargin()
{
    x_ = x_ < 0 ? 0 : x_ > MAP_SIZE - width_ ? MAP_SIZE - width_ : x_;
    y_ = y_ < 0 ? 0 : y_ > MAP_SIZE - height_ ? MAP_SIZE - height_ : y_;
}
